/**
* @Author: Lanhai Bai
* @Date: 2019/12/23 14:21
* @Description:
 */
package control

import (
	"bytes"
	"card-platform/config"
	"crypto/md5"
	"crypto/tls"
	"encoding/hex"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"pkg/cfg"
	"sort"
	"strings"
	"time"
)


type WeixinPay struct {
}

func NewWeixinOrder() *WeixinPay {
	return &WeixinPay{}
}

type TransfersResult struct {
	ReturnCode     string `xml:"return_code"` // SUCCESS/FAIL SUCCESS退款申请接收成功，结果通过退款查询接口查询 FAIL 提交业务失败
	ReturnMsg      string `xml:"return_msg"`  // 列表详见错误码列表
	ResultCode     string `xml:"result_code"`
	ErrCode        string `xml:"err_code"`
	ErrCodeDes     string `xml:"err_code_des"`
	MchAppid       string `xml:"mch_appid"`
	Mchid          string `xml:"mchid"`
	DeviceInfo     string `xml:"device_info"`
	NonceStr       string `xml:"nonce_str"`
	PartnerTradeNo string `xml:"partner_trade_no"`
	PaymentNo      string `xml:"payment_no"`
	PaymentTime    string `xml:"payment_time"`
}

//企业转账
// username 收款人姓名 openId 收款人openId partneTradeNo 商户订单号 payAmount 付款金额 desc 企业付款备注
func (t *WeixinPay) Transfers(username, openId, partneTradeNo string, payAmount uint64, desc string) (r TransfersResult, err error) {
	var p = url.Values{}
	p.Add("mch_appid", config.WxAppId)
	p.Add("mchid", config.WxMerchantId)
	p.Add("nonce_str", GetNonceStr())
	p.Add("partner_trade_no", partneTradeNo)
	p.Add("openid", openId)
	p.Add("check_name", "FORCE_CHECK")
	// NO_CHECK
	p.Add("re_user_name", username)
	p.Add("amount", fmt.Sprint(payAmount))
	p.Add("desc", desc)
	p.Add("spbill_create_ip", "222.209.188.95")
	p.Add("sign", t.sign(p, config.WxKey))
	//双向认证
	cert, err := tls.LoadX509KeyPair(config.WxPemPath, config.WxKeyPath)
	if err != nil {
		fmt.Println("企业转账请求参数err:", err)
		return r, err
	}
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			RootCAs:      nil,
			Certificates: []tls.Certificate{cert},
		},
	}
	fmt.Println("企业转账请求参数p:", p)
	cfg.LogInfo("企业转账请求参数:", p)
	client := &http.Client{Transport: tr}
	req, err := client.Post("https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers", "application/xml; charset=utf-8", strings.NewReader(URLValueToXML(p)))
	if err != nil {
		return r, err
	}
	defer req.Body.Close()
	data, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return r, err
	}
	fmt.Println("微信返回数据 data:", string(data))
	cfg.LogInfo("企业转账请求参数 data:", string(data))
	if err = xml.Unmarshal(data, &r); err != nil {
		return r, err
	}
	return r, nil
}

type GettransferInfoResult struct {
	ReturnCode     string `xml:"return_code"`      //  SUCCESS/FAIL SUCCESS退款申请接收成功，结果通过退款查询接口查询 FAIL 提交业务失败
	ReturnMsg      string `xml:"return_msg"`       //  列表详见错误码列表
	ResultCode     string `xml:"result_code"`      //  业务结果
	MchId          string `xml:"mch_id"`           //  微信支付分配的商户号
	AppId          string `xml:"appid"`            //  商户号appid
	DetailId       string `xml:"detail_id"`        //  付款单号
	PartnerTradeNo string `xml:"partner_trade_no"` //  商户单号
	Status         string `xml:"status"`           //  状态 SUCCESS:转账成功 FAILED:转账失败 PROCESSING:处理中
	PaymentAmount  string `xml:"payment_amount"`   //  付款金额单位为“分”
	OpenId         string `xml:"openid"`           //  收款用户openid
	TransferTime   string `xml:"transfer_time"`    //  发起转账的时间
	PaymentTime    string `xml:"payment_time"`     //  企业付款成功时间
	TransferName   string `xml:"transfer_name"`    //  收款用户姓名
	Desc           string `xml:"desc"`             //  企业付款备注
}

//企业付款查询
func (t *WeixinPay) Gettransferinfo(partneTradeNo string) (r GettransferInfoResult, err error) {
	var p = url.Values{}
	p.Add("appid", config.WxAppId)
	p.Add("mch_id", config.WxMerchantId)
	p.Add("nonce_str", GetNonceStr())
	p.Add("partner_trade_no", partneTradeNo)
	p.Add("sign", t.sign(p, config.WxKey))
	//双向认证
	cert, err := tls.LoadX509KeyPair(config.WxPemPath, config.WxKeyPath)
	if err != nil {
		return r, err
	}
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			RootCAs:      nil,
			Certificates: []tls.Certificate{cert},
		},
	}
	cfg.LogInfo("查询企业转账账单请求参数:", p)
	fmt.Println("查询企业转账账单请求参数:", p)
	client := &http.Client{Transport: tr}
	req, err := client.Post("https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo", "application/xml; charset=utf-8", strings.NewReader(URLValueToXML(p)))
	if err != nil {
		return r, err
	}
	defer req.Body.Close()
	data, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return r, err
	}
	cfg.LogInfo("查询企业转账账单 data:", string(data))
	fmt.Println("查询企业转账账单 data:", string(data))
	if err = xml.Unmarshal(data, &r); err != nil {
		return r, err
	}
	return r, nil
}

//支付订单下单
func (wx *WeixinPay) MakeOrder(openId, orderId string, payAmount uint64, createTime int64) (interface{}, error) {
	var tradeType = "JSAPI"
	expire := time.Unix(createTime+config.ORDER_PAY_TTL, 0).Format("20060102150405")
	content := "商城订单"
	var p = url.Values{}
	p.Add("appid", config.WxAppId)
	p.Add("mch_id", config.WxMerchantId)
	p.Add("body", content)
	p.Add("out_trade_no", orderId)
	p.Add("nonce_str", GetNonceStr())
	p.Add("total_fee", fmt.Sprintf("%d", payAmount))
	p.Add("spbill_create_ip", "127.0.0.1")
	p.Add("notify_url", config.Host+config.WXNotifyCallbackURL)
	p.Add("trade_type", tradeType)
	p.Add("time_expire", expire)
	p.Add("openid", openId)
	p.Add("sign", wx.sign(p, config.WxKey))

	cfg.LogInfo("请求参数:", p)

	req, err := http.Post("https://api.mch.weixin.qq.com/pay/unifiedorder", "application/xml; charset=utf-8", strings.NewReader(URLValueToXML(p)))
	if err != nil {
		return nil, err
	}

	defer req.Body.Close()
	data, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return nil, err
	}
	cfg.LogInfof("order_weixin MakeOrder response data: %v", string(data))
	if _, err := wx.VerifySign(data, config.WxKey); err != nil {
		return nil, err
	}

	var result *orderResult

	cfg.LogInfo("data:", string(data))

	if err = xml.Unmarshal(data, &result); err != nil {
		return nil, err
	}

	if result.PrepayId == "" {
		return nil, errors.New("返回字段为空")
	}

	//生成支付参数
	ti := time.Now().Unix()
	var payParam = url.Values{}
	payParam.Add("appId", config.WxAppId)
	payParam.Add("nonceStr", GetNonceStr())
	payParam.Add("package", fmt.Sprintf("prepay_id=%s", result.PrepayId))
	payParam.Add("timeStamp", fmt.Sprintf("%d", ti))
	payParam.Add("signType", "MD5")
	re := struct {
		Sign      string `json:"sign"`
		PrepayId  string `json:"prepayid"`
		PartnerId string `json:"partnerid"`
		NonceStr  string `json:"noncestr"`
		Package   string `json:"package"`
		TimeStamp int64  `json:"timestamp"`
		AppID     string `json:"appid"`
		SignType  string `json:"signType"`
	}{
		Sign:      wx.sign(payParam, config.WxKey),
		PrepayId:  result.PrepayId,
		PartnerId: result.MchId,
		NonceStr:  payParam.Get("nonceStr"),
		Package:   payParam.Get("package"),
		TimeStamp: ti,
		AppID:     config.WxAppId,
		SignType:  "MD5",
	}

	cfg.LogInfof("order_weixin MakeOrder response data: %+v", re)
	return re, nil
}

//支付订单下单 https://api.mch.weixin.qq.com/pay/closeorder
func (wx *WeixinPay) CloseOrder(orderId string) (orderResult, error) {
	var tradeType = "JSAPI"
	var p = url.Values{}
	p.Add("appid", config.WxAppId)
	p.Add("mch_id", config.WxMerchantId)
	p.Add("out_trade_no", orderId)
	p.Add("nonce_str", GetNonceStr())
	p.Add("trade_type", tradeType)
	p.Add("sign", wx.sign(p, config.WxKey))
	cfg.LogInfo("请求参数:", p)
	var result orderResult
	req, err := http.Post("https://api.mch.weixin.qq.com/pay/closeorder", "application/xml; charset=utf-8", strings.NewReader(URLValueToXML(p)))
	if err != nil {
		return result, err
	}

	defer req.Body.Close()
	data, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return result, err
	}
	cfg.LogInfof("order_weixin MakeOrder response data: %v", string(data))
	if _, err := wx.VerifySign(data, config.WxKey); err != nil {
		return result, err
	}

	cfg.LogInfo("data:", string(data))
	if err = xml.Unmarshal(data, &result); err != nil {
		return result, err
	}
	return result, nil
}

//退款。微信退款是异步的，这里做了处理。
func (t *WeixinPay) RefundOrderPay(payAmount, refundAmount uint64, tradeNo, outRequestNo, refundReason string) (r refundResult, err error) {
	var p = url.Values{}
	p.Add("appid", config.WxAppId)
	p.Add("mch_id", config.WxMerchantId)
	p.Add("nonce_str", GetNonceStr())
	p.Add("out_trade_no", tradeNo)
	p.Add("total_fee", fmt.Sprintf("%d", payAmount))
	p.Add("refund_fee", fmt.Sprintf("%d", refundAmount))
	p.Add("out_refund_no", outRequestNo)
	p.Add("refund_desc", refundReason)
	p.Add("notify_url", config.Host+config.RefundCallBackURL)
	p.Add("sign", t.sign(p, config.WxKey))

	//双向认证
	cert, err := tls.LoadX509KeyPair(config.WxPemPath, config.WxKeyPath)
	if err != nil {
		return r, err
	}
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			RootCAs:      nil,
			Certificates: []tls.Certificate{cert},
		},
	}

	cfg.LogInfo("退款请求参数:", p)

	client := &http.Client{Transport: tr}
	req, err := client.Post("https://api.mch.weixin.qq.com/secapi/pay/refund", "application/xml; charset=utf-8", strings.NewReader(URLValueToXML(p)))
	if err != nil {
		return r, err
	}

	defer req.Body.Close()
	data, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return r, err
	}

	cfg.LogInfo("微信退款 data:", string(data))
	if _, err := t.VerifySign(data, config.WxKey); err != nil {
		cfg.LogErr("退款验签失败:", err)
		return r, err
	}
	result := refundResult{}
	if err = xml.Unmarshal(data, &result); err != nil {
		return result, err
	}
	return result, nil
}

// 订单查询
func (t *WeixinPay) QueryOrder(orderId string) (*refundResult, error) {
	var p = url.Values{}
	p.Add("appid", config.WxAppId)
	p.Add("mch_id", config.WxMerchantId)
	p.Add("nonce_str", GetNonceStr())
	p.Add("out_trade_no", orderId)
	p.Add("sign", t.sign(p, config.WxKey))

	req, err := http.Post("https://api.mch.weixin.qq.com/pay/orderquery", "application/xml; charset=utf-8", strings.NewReader(URLValueToXML(p)))
	if err != nil {
		return nil, err
	}

	defer req.Body.Close()
	data, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return nil, err
	}

	if _, err := t.VerifySign(data, config.WxKey); err != nil {
		return nil, err
	}

	var result *refundResult
	if err = xml.Unmarshal(data, &result); err != nil {
		return nil, err
	}

	return result, nil
}

// 订单查询
func (t *WeixinPay) QueryRefundOrder(outRefundNo string) (*refundQueryResult, error) {
	var p = url.Values{}

	p.Add("appid", config.WxAppId)
	p.Add("mch_id", config.WxMerchantId)
	p.Add("nonce_str", GetNonceStr())
	p.Add("out_refund_no", outRefundNo)

	p.Add("sign", t.sign(p, config.WxKey))

	req, err := http.Post("https://api.mch.weixin.qq.com/pay/refundquery", "application/xml; charset=utf-8", strings.NewReader(URLValueToXML(p)))
	if err != nil {
		return nil, err
	}

	defer req.Body.Close()
	data, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return nil, err
	}

	if _, err := t.VerifySign(data, config.WxKey); err != nil {
		return nil, err
	}

	cfg.LogInfo("退款订单查询:", string(data))
	var result *refundQueryResult
	if err = xml.Unmarshal(data, &result); err != nil {
		return nil, err
	}
	return result, nil
}

func (t WeixinPay) sign(param url.Values, key string) string {
	var keys []string
	for key := range param {
		var value = strings.TrimSpace(param.Get(key))
		if len(value) > 0 {
			keys = append(keys, key+"="+value)
		}
	}
	sort.Strings(keys)
	keys = append(keys, "key="+key)
	var src = strings.Join(keys, "&")
	md5Ctx := md5.New()
	md5Ctx.Write([]byte(src))
	cipherStr := md5Ctx.Sum(nil)
	return strings.ToUpper(hex.EncodeToString(cipherStr))
}

// 验证签名
func (t *WeixinPay) VerifySign(data []byte, key string) (bool, error) {
	var param = make(XMLMap)
	err := xml.Unmarshal(data, &param)
	if err != nil {
		return false, err
	}
	return t.verifySign(url.Values(param), key)
}

func (t *WeixinPay) verifySign(param url.Values, key string) (bool, error) {
	var code = param.Get("return_code")
	if code == "FAIL" {
		var msg = param.Get("return_msg")
		if msg == "" {
			msg = param.Get("retmsg")
		}
		return false, errors.New(msg)
	}
	code = param.Get("result_code")
	if code == "FAIL" {
		var msg = param.Get("err_code_des")
		return false, errors.New(msg)
	}

	// 验证签名
	var sign = param.Get("sign")
	delete(param, "sign")
	if sign == "" {
		return false, errors.New("签名验证失败")
	}
	var sign2 = t.sign(param, key)
	if sign == sign2 {
		return true, nil
	}
	return false, errors.New("签名验证失败")
}

// 生成随机字符串
func GetNonceStr() (nonceStr string) {
	chars := "abcdefghijklmnopqrstuvwxyz0123456789"
	var r = rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < 32; i++ {
		idx := r.Intn(len(chars) - 1)
		nonceStr += chars[idx : idx+1]
	}
	return nonceStr
}

// url转xml
func URLValueToXML(m url.Values) string {
	var xmlBuffer = &bytes.Buffer{}
	xmlBuffer.WriteString("<xml>")

	for key := range m {
		var value = m.Get(key)
		if key == "total_fee" || key == "refund_fee" || key == "execute_time" {
			xmlBuffer.WriteString("<" + key + ">" + value + "</" + key + ">")
		} else {
			xmlBuffer.WriteString("<" + key + "><![CDATA[" + value + "]]></" + key + ">")
		}
	}
	xmlBuffer.WriteString("</xml>")
	return xmlBuffer.String()
}

type XMLMap url.Values
type xmlMapEntry struct {
	XMLName xml.Name
	Value   string `xml:",chardata"`
}

func (m XMLMap) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	for {
		var e xmlMapEntry
		err := d.Decode(&e)
		if err == io.EOF {
			break
		} else if err != nil {
			return err
		}
		(m)[e.XMLName.Local] = []string{e.Value}
	}
	return nil
}

type orderResult struct {
	Appid      string `xml:"appid"`     //调用接口提交的应用ID
	MchId      string `xml:"mch_id"`    //调用接口提交的商户号
	NonceStr   string `xml:"nonce_str"` //微信返回的随机字符串
	ResultCode string `xml:"result_code"`
	ErrCode    string `xml:"err_code"`
	ErrCodeDes string `xml:"err_code_des"`
	TradeType  string `xml:"trade_type"` //调用接口提交的交易类型，取值如下：JSAPI，NATIVE，APP，详细说明见参数规定
	PrepayId   string `xml:"prepay_id"`  //微信生成的预支付回话标识，用于后续接口调用中使用，该值有效期为2小时
}

type refundResult struct {
	ReturnCode          string `xml:"return_code"` // SUCCESS/FAIL SUCCESS退款申请接收成功，结果通过退款查询接口查询 FAIL 提交业务失败
	ReturnMsg           string `xml:"return_msg"`  // 列表详见错误码列表
	ResultCode          string `xml:"result_code"`
	ErrCode             string `xml:"err_code"`
	ErrCodeDes          string `xml:"err_code_des"`
	AppId               string `xml:"appid"`
	MCHId               string `xml:"mch_id"`
	NonceStr            string `xml:"nonce_str"`
	Sign                string `xml:"sign"`
	TransactionId       string `xml:"transaction_id"`
	OutTradeNo          string `xml:"out_trade_no"`
	OutRefundNo         string `xml:"out_refund_no"`
	RefundId            string `xml:"refund_id"`
	RefundFee           int    `xml:"refund_fee"`
	SettlementRefundFee int    `xml:"settlement_refund_fee"`
	TotalFee            int    `xml:"total_fee"`
	SettlementTotalFee  int    `xml:"settlement_total_fee"`
	FeeType             string `xml:"fee_type"`
	CashFee             int    `xml:"cash_fee"`
	CashFeeType         string `xml:"cash_fee_type"`
	CashRefundFee       int    `xml:"cash_refund_fee"`
	CouponRefundFee     int    `xml:"coupon_refund_fee"`
	CouponRefundCount   int    `xml:"coupon_refund_count"`
}

type refundQueryResult struct {
	ReturnCode         string `xml:"return_code"` // SUCCESS/FAIL SUCCESS退款申请接收成功，结果通过退款查询接口查询 FAIL 提交业务失败
	ReturnMsg          string `xml:"return_msg"`  // 列表详见错误码列表
	ResultCode         string `xml:"result_code"`
	ErrCode            string `xml:"err_code"`
	ErrCodeDes         string `xml:"err_code_des"`
	AppId              string `xml:"appid"`
	MCHId              string `xml:"mch_id"`
	NonceStr           string `xml:"nonce_str"`
	Sign               string `xml:"sign"`
	TotalRefundCount   int    `xml:"total_refund_count"`
	TransactionId      string `xml:"transaction_id"`
	OutTradeNo         string `xml:"out_trade_no"`
	TotalFee           int    `xml:"total_fee"`
	SettlementTotalFee int    `xml:"settlement_total_fee"`
	FeeType            string `xml:"fee_type"`
	CashFee            int    `xml:"cash_fee"`
}

type CallbackResult struct {
	ReturnCode    string `xml:"return_code"`    // SUCCESS、FIAL
	ReturnMsg     string `xml:"return_msg"`     // 返回信息 OK
	AppId         string `xml:"appid"`          // 微信开放平台审核通过的应用APPID
	MchId         string `xml:"mch_id"`         // 微信支付分配的商户号
	DeviceInfo    string `xml:"device_info"`    // 微信支付分配的终端设备号
	NonceStr      string `xml:"nonce_str"`      // 随机字符串，不长于32位
	Sign          string `xml:"sign"`           // 签名
	ResultCode    string `xml:"result_code"`    // SUCCESS/FAIL
	ErrCode       string `xml:"err_code"`       // 错误返回的信息描述
	ErrCodeDes    string `xml:"err_code_des"`   // 错误返回的信息描述
	OpenId        string `xml:"openid"`         // 用户在商户appid下的唯一标识
	IsSubscribe   string `xml:"is_subscribe"`   // 用户是否关注公众账号，Y-关注，N-未关注
	TradeType     string `xml:"trade_type"`     // APP
	BankType      string `xml:"bank_type"`      // 银行类型，采用字符串类型的银行标识，银行类型见银行列表
	TotalFee      int    `xml:"total_fee"`      // 订单总金额，单位为分
	FeeType       string `xml:"fee_type"`       // 货币类型，符合ISO4217标准的三位字母代码，默认人民币：CNY，其他值列表详见货币类型
	CashFee       int    `xml:"cash_fee"`       // 现金支付金额订单现金支付金额，详见支付金额
	CashFeeType   string `xml:"cash_fee_type"`  // 货币类型，符合ISO4217标准的三位字母代码，默认人民币：CNY，其他值列表详见货币类型
	CouponFee     int    `xml:"coupon_fee"`     // 代金券或立减优惠金额<=订单总金额，订单总金额-代金券或立减优惠金额=现金支付金额，详见支付金额
	CouponCount   int    `xml:"coupon_count"`   // 代金券或立减优惠使用数量
	TransactionId string `xml:"transaction_id"` // 微信支付订单号
	OutTradeNo    string `xml:"out_trade_no"`   // 商户系统内部订单号，要求32个字符内，只能是数字、大小写字母_-|*@ ，且在同一个商户号下唯一。
	Attach        string `xml:"attach"`         // 商家数据包，原样返回
	TimeEnd       string `xml:"time_end"`       // 支付完成时间，格式为yyyyMMddHHmmss，如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
}